﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Xml;
using System.ComponentModel;
using System.Reflection;

//using http://james.newtonking.com/json to read through json
namespace ConsoleApplication2
{
    class MediaData
    {
        //Api keys required to access data from media sites, isbndb.com and themoviedb.org
        private string TMDb_apiKey = "ffb2c95850522361084f2387925098f8";
        private string ISBNDb_apiKey = "252FGT1E"; //limited to 500requests per day, due to payment restrictions

        private string movieBaseURL = "http://api.themoviedb.org/3/";
        private string baseCoverUrl = "http://d3gtl9l2a4fn1j.cloudfront.net/t/p/original"; //the movie db returns a cover-image id, this is the base url for that
        private string gameBaseURL = "http://thegamesdb.net/api/";
        private string bookBaseURL = "http://isbndb.com/api/v2/xml/";

        //To keep track of total movies actually downloaded and number of expectet movies
        public int TotalMovieCounter { get; private set; }
        private int TotalExpectet = 0;

        
        public MediaData()
        {
            bookBaseURL += ISBNDb_apiKey + "/";
        }

        //Because of problems with our database not accepting (large) enums we have had to save them in an int list instead
        private List<int> convertEnumToInt<T>(List<T> genreIn)
        {
            List<int> toReturn = new List<int>();
        
            foreach (var genre in genreIn)
            {
                toReturn.Add(Convert.ToInt32(genre));
            }

            return toReturn;
        }
        private string getData(string URL)
        {
            //Heavily based on code snippet from; https://www.themoviedb.org/talk/52640d8f19c2951a08042ce6
            var request = (HttpWebRequest)WebRequest.Create(URL);
            request.Method = "GET";
            request.ContentLength = 0;
            string responseContent = "";
            using (var response = request.GetResponse() as HttpWebResponse)
            {
                if (null != response && HttpStatusCode.OK == response.StatusCode) //makes sure the response is valid
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                    {
                        responseContent = reader.ReadToEnd();
                    }
            }
                else return null; //Might need to implement a status code appropriate for error
            }

            return responseContent;
        }

        private List<Book> getXmlData(string xmlFormattedString)
        {
            //variables used to build book object
            List<Book> toReturn = new List<Book>();
            string title = "";
            List<Author> authors = new List<Author>();
            List<Genre> genres = new List<Genre>();

            //copies xml string into a xml document format
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlFormattedString);

            //splits the document up into nodes, selecting the nodes called "data"
            XmlNodeList nodes = xmlDoc.DocumentElement.SelectNodes("//data");

            //runs through each "data" node
            foreach (XmlNode node in nodes)
            {
                //clears the genre and authors list, so that we can fill new data in them
                genres.Clear();
                authors.Clear();
                //because its not a necessity for a book to have an author
                if (node.SelectSingleNode("author_data") != null)
                {
                    string author = node.SelectSingleNode("author_data").SelectSingleNode("name").InnerText;
                    authors.Add(new Author(author));
                }
                title = node.SelectSingleNode("title").InnerText;
                Genre currGenre;
                XmlNodeList genreNodes = node.SelectNodes("subject_ids");
                foreach (XmlNode genreNode in genreNodes)
                {
                    if (Enum.TryParse<Genre>(genreNode.InnerText.Replace("_", ""), out currGenre))
                        genres.Add(currGenre);
                }
                toReturn.Add(new Book(authors, title, convertEnumToInt<Genre>(genres)));
            }
            return toReturn;
        }

        public Movie getJsonData(string jsonFormattedString)
        {
            //variables needed to build the 
            List<Genre> genres = new List<Genre>();
            string title = "";
            string director = "";
            List<Actor> actors = new List<Actor>();
            string coverImage = "";

            //To get the data we need from the json object, we do string manipulation. There are a arguably clearer way of doing it by making throw away classes with matching attributes with the json object. But since the json object contains alot of information we dont need this felt faster
            dynamic parsedObject = JsonConvert.DeserializeObject(jsonFormattedString);
            foreach (dynamic entry in parsedObject) //runs through each object in the json object
            {
                string name = entry.Name; //gets the name of the current json object
                dynamic value = entry.Value; //gets the information of the current json object
                if (name.Equals("genres"))
                {
                    foreach (dynamic d in value)
                    {
                        string currValue = Convert.ToString(d);
                        string[] strings = currValue.Split('\"');
                        string prev = "";
                        for (int i = 0; i < strings.Length; i++)
                        {
                            if (prev.Equals("name"))
                            {
                                string temp = strings[i + 1].Replace(" ", "").Replace("-", "").ToLower();
                                genres.Add((Genre)Enum.Parse(typeof(Genre), temp));
                            }
                            prev = strings[i];
                        }
                    }
                }
                else if (name.Equals("original_title")) title = Convert.ToString(value);
                else if (name.Equals("poster_path")) coverImage = baseCoverUrl + Convert.ToString(value);
                else if (name.Equals("credits"))
                {
                    string valueString = Convert.ToString(value);
                    String[] sections = valueString.Split('[', ']');
                    //actors
                    string[] cast = sections[1].Split('{', '}');
                    foreach (string s in cast)
                        if (s.Contains("name"))
                        {
                            string[] actorInfo = s.Split(',');
                            string[] actorNameLine = actorInfo[1].Split(':');
                            actors.Add(new Actor(actorNameLine[1].Replace("\"", "").Trim()));
                        }

                    //director
                    string[] crew = sections[3].Split('{', '}');
                    if (crew.Length > 2)
                    {
                        string[] directorInfo = crew[1].Split(',');
                        string[] directorNameInfo = directorInfo[1].Split(':');
                        director = directorNameInfo[1].Replace("\"", "").Trim();
                    }
                }
            }

            //constructs movie object
            Director newDirector = new Director(director);
            Movie toReturn = new Movie(newDirector, actors, title, convertEnumToInt<Genre>(genres));
            foreach (Actor a in actors) a.AssociatedMedia.Add(toReturn);
            newDirector.AssociatedMedia.Add(toReturn);

            return toReturn;
        }

        #region movies
        public Movie getMovieById(int id)
        {
            TotalExpectet++;
            string URL = string.Format("{2}movie/{0}?api_key={1}&append_to_response=credits", Convert.ToString(id), TMDb_apiKey, movieBaseURL);

            return (Movie)getJsonData(getData(URL));
            }

        public List<Movie> getMovieListByGenreId(int genreId, bool adultFilms, bool includeAll)
        {
            int totalPages = 1;
            List<Movie> movieList = new List<Movie>();
 
            string URLToFindPage = string.Format("{2}genre/{0}/movies?api_key={1}", //at the moment this list does not include; adult films or movies that have been rated ten or less times
                Convert.ToString(genreId),
                TMDb_apiKey,
                movieBaseURL,
                totalPages
                );
            // The returned data is split into pages, 20 movies per page.
            // to find the number of pages, so we can run through them, we read the json obejct.
            dynamic objectToFindPages = JsonConvert.DeserializeObject(getData(URLToFindPage));
            foreach (dynamic entry in objectToFindPages) //runs through the objects in the json object, to find the total pages
            {
                string name = entry.Name;
                dynamic value = entry.Value;
                if (name.Equals("total_pages"))
                    totalPages = Convert.ToInt32(value);
                else if (name.Equals("total_results")) TotalMovieCounter += Convert.ToInt32(value);
        }

            //runs through every page to get all movies
            for (int i = 1; i <= totalPages; i++)
        {
                string URL = string.Format("{2}genre/{0}/movies?api_key={1}&page={3}", //at the moment this list does not include; adult films or movies that have been rated ten or less times
                Convert.ToString(genreId), 
                TMDb_apiKey, 
                    movieBaseURL,
                    i
                );
                Console.WriteLine("Page number: " + i);
                dynamic parsedObject = JsonConvert.DeserializeObject(getData(URL));
                foreach (dynamic entry in parsedObject)
                {
                    string name = entry.Name;
                    dynamic value = entry.Value;
                    if (name.Equals("results"))
                    {
                        string valueString = Convert.ToString(value);
                        string[] sections = valueString.Split('{');
                        foreach (string s in sections)
                        {
                            string[] lines = s.Split(',');
                            if (lines.Length > 2)
                            {
                                string[] id = lines[2].Split(':');
                                movieList.Add(getMovieById(Convert.ToInt32(id[1].Trim())));
                            }
                        }
                    }
                }
            }
            Console.WriteLine("Total: " + TotalMovieCounter + "/ " + TotalExpectet);
            return movieList;
        }

        public List<Movie> GetAllMovies()
        {
            string URL = string.Format("{1}genre/list?api_key={0}", TMDb_apiKey, movieBaseURL);
            List<Movie> movieList = new List<Movie>();

            dynamic parsedObject = JsonConvert.DeserializeObject(getData(URL));
            foreach (dynamic entry in parsedObject) //runs through every object in the json object
            {

                string name = entry.Name;
                dynamic value = entry.Value;
                string valueString = Convert.ToString(value);
                string[] sections = valueString.Split('{');
                foreach (string s in sections)
                {
                    string[] infoId = s.Split(':');
                    if (infoId.Length > 2)
                    {
                        string[] id = infoId[1].Split(',');
                        movieList.AddRange(getMovieListByGenreId(Convert.ToInt32(id[0].Trim()), false, false));
                    }
                }
            }

            return movieList;
        }
        #endregion

        #region games
        public VideoGame getGameByName(string name)
        {
            //Example from here: http://forums.asp.net/t/1881390.aspx?Convert+XML+data+to+string+c+

            //Gets the information from the website as a string.
            string xmlString = getData(gameBaseURL + "GetGame.php?name=" + name);

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");
            
            string title = null;
            List<Platform> platforms = new List<Platform>();
            string gamestudio = null;
            string publisher = null;
            List<Genre> genres = new List<Genre>();

            //Finds information from every node by its specific node.
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("GameTitle").InnerText == name)
                {
                    title = node["GameTitle"].InnerText;
                    if (Enum.IsDefined(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")))
                    {
                        platforms.Add((Platform)Enum.Parse(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")));
                    gamestudio = node["Developer"].InnerText;
                    publisher = node["Publisher"].InnerText;
                    }
                if (node.SelectSingleNode("Genres") != null)
                {
                    XmlNode genreNode = node["Genres"];
                    foreach (XmlNode genre in genreNode.SelectNodes("genre"))
                            genres.Add((Genre)Enum.Parse(typeof(Genre), genre.InnerText.ToLower().Replace("-", "")));
                }
                }
            }

            //Creating a new VideoGame
            GameStudio newGamestudio = new GameStudio(gamestudio);
            Publisher newPublisher = new Publisher(publisher);
            VideoGame toReturn = new VideoGame(newGamestudio, newPublisher, convertEnumToInt<Platform>(platforms), title, convertEnumToInt<Genre>(genres));
            
            return toReturn;
        }

        public List<VideoGame> getPlatformList(string platform)
        {
            int id = 0;

            //Create an XML Document and loads the xmlString in the XML Document.
            string xmlString = getData(gameBaseURL + "GetPlatformsList.php");

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Platform nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Platform");

            //Finding the id for the specific Platform
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("name").InnerText == platform)
                {
                    id = Convert.ToInt32(node.SelectSingleNode("id").InnerText);
                }
            }

            return getGamesByPlatformId(id, platform);
        }

        public List<VideoGame> getGamesByPlatformId(int id, string platform)
        {  
            string xmlString = getData(gameBaseURL + "GetPlatformGames.php?platform=" + Convert.ToString(id));

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");

            string gameTitle = null;
            List<VideoGame> toReturn = new List<VideoGame>(); 

            //Finds information about every games.
            foreach (XmlNode node in nodes)
            {
                gameTitle = node.SelectSingleNode("GameTitle").InnerText;
                toReturn.Add(getGameByName(gameTitle, platform));
            }

            return toReturn;
        }

        public VideoGame getGameByName(string name, string platform)
        {
            //Example from here: http://forums.asp.net/t/1881390.aspx?Convert+XML+data+to+string+c+

            //Create an XML Document and loads the xmlString in the XML Document.
            string xmlString = getData(gameBaseURL + "GetGame.php?name=" + name);

            //Create an XML Document and loads the xmlString in the XML Document.
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmlString);

            //Get node List of Game nodes.
            XmlNodeList nodes = doc.DocumentElement.SelectNodes("//Game");

            string title = null;
            List<Platform> platforms = new List<Platform>();
            string gamestudio = null;
            string publisher = null;
            List<Genre> genreList = new List<Genre>();

            //Finds information from every note by its specific node.
            foreach (XmlNode node in nodes)
            {
                if (node.SelectSingleNode("GameTitle").InnerText == name && node.SelectSingleNode("Platform").InnerText == platform)
                {
                    
                    title = node["GameTitle"].InnerText;
                    platforms.Add((Platform)Enum.Parse(typeof(Platform), node["Platform"].InnerText.Replace(" ", "")));
                    if (node.SelectSingleNode("Developer") != null)
                        gamestudio = node["Developer"].InnerText;
                    if (node.SelectSingleNode("Publisher") != null)
                        publisher = node["Publisher"].InnerText;
                    if (node.SelectSingleNode("Genres") != null)
                    {
                        XmlNode genreNode = node["Genres"];
                        foreach (XmlNode genre in genreNode.SelectNodes("genre"))
                            genreList.Add((Genre)Enum.Parse(typeof(Genre), genre.InnerText.Replace("-", "").Replace(" ", "")));
                    }
                    break;
            }
            }

            //Constructs
            GameStudio newGameStudio = new GameStudio(gamestudio);
            Publisher newPublisher = new Publisher(publisher);
            VideoGame toReturn = new VideoGame(newGameStudio, newPublisher, convertEnumToInt<Platform>(platforms), title, convertEnumToInt<Genre>(genreList));

            return toReturn;
        }
             
        #endregion

        #region books
        public Book GetBookByTitle(string title)
        {
            title = title.Replace(" ", "_");
            string URL = string.Format("{0}book/{1}",
                bookBaseURL,
                title
                );

            return getXmlData(getData(URL))[0];
        }

        public List<Book> GetBooksByGenre(string genre)
        {
            int totalPages = 0;

            string baseURL = string.Format("{0}books?q={1}",
                bookBaseURL,
                genre
                );
            string xmlData = getData(baseURL);
            List<Book> toReturn = new List<Book>();

            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(xmlData);

            // The returned data is split into pages.
            // to find the number of pages, so we can run through them, we read the page_count xml node.
            XmlNode node = xmlDoc.SelectSingleNode("//page_count");
            totalPages = Convert.ToInt32(node.InnerText);
            Console.WriteLine(totalPages);
            toReturn.AddRange(getXmlData(xmlData));
            Console.Write("Got page 1!");

            for (int i = 2; i <= totalPages; i++)
            {
                string pageNumber = "&page=" + i;
                string URL = baseURL + pageNumber;

                try
                {
                    toReturn.AddRange(getXmlData(getData(URL)));
                    Console.Write("\rGot page " + i + "!");
                }
                catch (WebException e)
                {
                    Console.WriteLine("\nThere were an error accessing the page, page was skipped: " + e.Message);
                }
        }
            return toReturn;
        }
        #endregion
    }    
}
